#include "channellist.h"
#include "channel.h"

#include <QSettings>
#include <QtConcurrentRun>
#include <QDir>
#include <QFile>
#include <QTemporaryFile>
#include <QDomDocument>
#include <QXmlQuery>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>

ChannelList::ChannelList(QObject* parent):
        QObject(parent)
{
}

ChannelList::~ChannelList()
{
    cancel();
    foreach (Channel* c, channels)
        delete c;
}

int ChannelList::count() const
{
    return channels.count();
}

Channel* ChannelList::at(int index) const
{
    if (index < 0 || index >= channels.count())
        return 0;
    return channels[index];
}

void ChannelList::update(bool force)
{
    if (!watcher.isRunning()) {
        canceled = false;
        QFuture<void> job = QtConcurrent::run(this, &ChannelList::updateThreadFunc, force);
        watcher.setFuture(job);
    }
}

void ChannelList::updateThreadFunc(bool force)
{
    // try to load cached data first
    QSettings settings;
    if (channels.isEmpty() && settings.contains("feed/cacheFile")) {
        QFile storeFile(settings.value("feed/cacheFile").toString());
        qDebug() << "Cachefile " << storeFile.fileName();
        if (storeFile.exists() && storeFile.open(QIODevice::ReadOnly)) {
            qDebug() << "Restoring from " << storeFile.fileName();
            // read channel list
            QDataStream stream(&storeFile);
            int numChannels, numPrograms, numStreams;
            stream >> lastUpdate >> numChannels;
            for (int i = 0; !canceled && i < numChannels; ++i) {
                Channel* c = new Channel;
                stream >> c->title >> c->description >> c->link >> c->guid >> numPrograms;
                for (int j = 0; !canceled && j < numPrograms; ++j) {
                    Program* p = new Program;
                    stream >> p->title >> p->description >> p->link >> p->guid >> p->pubDate
                            >> p->duration >> p->thumbnail >> numStreams;
                    for (int k = 0; !canceled && k < numStreams; ++k) {
                        Stream* s = new Stream;
                        stream >> s->url >> s->mimeType >> s->size >> s->bitrate >> s->isDefault;
                        p->streams.append(s);
                    }
                    c->programs.append(p);
                }
                channels.append(c);
            }
        }
    }

    // update only every 30 minutes
    int interval = settings.value("feed/minUpdateInterval").toInt();
    if (canceled || (!force && lastUpdate.isValid() &&
                     lastUpdate.addSecs(interval*60) > QDateTime::currentDateTime())) {
        qDebug() << "User canceled update or cache file too fresh";
        emit updated(!canceled && !channels.isEmpty());
        return;
    }

    // proceed to query new data from feed
    bool success = updateChannels();
    qDebug() << "Channel query success=" << success;
    emit updated(success); // UI can continue now

    // if query was not successful, stop here, otherwise continue by saving cache file
    if (!success)
        return;

    // update cache timestamp
    lastUpdate = QDateTime::currentDateTime();

    // create new temporary file
    QString name = QDir::temp().relativeFilePath(QCoreApplication::applicationName());
    QTemporaryFile storeFile(name);
    storeFile.setAutoRemove(false);
    if (!storeFile.open()) {
        qDebug() << "Failed to create file " << storeFile.fileName();
        return;
    }

    // write channel list
    qDebug() << "Storing to " << storeFile.fileName();
    QDataStream stream(&storeFile);
    stream << lastUpdate << channels.count();
    foreach (Channel* c, channels) {
        stream << c->title << c->description << c->link << c->guid << c->programs.count();
        foreach (Program* p, c->programs) {
            stream << p->title << p->description << p->link << p->guid << p->pubDate
                   << p->duration << p->thumbnail << p->streams.count();
            foreach (Stream* s, p->streams) {
                stream << s->url << s->mimeType << s->size << s->bitrate << s->isDefault;
            }
        }
    }

    // save new file name and remove previous store
    QString oldFile = settings.value("feed/cacheFile").toString();
    if (!oldFile.isEmpty()) {
        qDebug() << "Removing old cache file " << oldFile;
        QFile::remove(oldFile);
    }
    settings.setValue("feed/cacheFile", storeFile.fileName());
}

bool ChannelList::updateChannels()
{
    QSettings settings;

    // create and configure the query object
    QXmlQuery query;
    QNetworkAccessManager http;
    connect(&http, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            parent(), SLOT(authenticationRequired(QNetworkReply*,QAuthenticator*)),
            Qt::BlockingQueuedConnection); // must be synchronous!
    query.setNetworkAccessManager(&http);

    // mark all existing channels as "not seen yet"
    foreach (Channel* c, channels)
        c->seen = false;

    // query all channel urls
    QUrl url = settings.value("feed/baseUrl").toString();
    if (!query.setFocus(url) || canceled)
        return false;
    query.setQuery("<c>{//item}</c>");
    QString channelsXml;
    query.evaluateTo(&channelsXml);
    //qDebug() << channelsXml;

    // start queries for all channels' programs
    QDomDocument channelDoc;
    channelDoc.setContent(channelsXml);
    for (QDomElement node = channelDoc.documentElement().firstChildElement("item");
         !canceled && !node.isNull();
         node = node.nextSiblingElement("item")) {
        Channel* c = new Channel;
        c->seen = true;
        c->title = node.firstChildElement("title").text();
        c->description = node.firstChildElement("description").text();
        c->link = url.resolved(node.firstChildElement("link").text() + "/flv.mediarss");
        c->guid = node.firstChildElement("guid").text();

        // update or add this channel
        bool found = false;
        QMutableListIterator<Channel*> i(channels);
        while (!found && i.hasNext()) {
            Channel*& channel = i.next();
            if (channel->guid == c->guid) {
                delete channel;
                channel = c;
                found = true;
            }
        }
        if (!found)
            channels.append(c);
    }
        
    // delete unseen channels and update seen channels
    QMutableListIterator<Channel*> i(channels);
    while (!canceled && i.hasNext()) {
        Channel*& c = i.next();
        if (c->seen) {
            emit progressInfo(c->title, channels.indexOf(c)+1, channels.count());
            c->update(query);
            qDebug() << "Channel " << c->title << " has " << c->programs.count() << " programs";
        }
        else {
            delete c;
            i.remove();
        }
    }
    return !canceled;
}

void ChannelList::cancel()
{
    if (watcher.isRunning()) {
        qDebug() << "Channel query canceling...";
        canceled = true;
        watcher.waitForFinished();
        qDebug() << "Channel query canceled";
    }
}
